/*
 * Copyright (C) Jahia Ltd. All rights reserved.
 *
 * This software is published under the terms of the Jahia Open Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.txt file.
 */
package org.jahia.sqlprofiler;

import java.util.ArrayList;

/**
 * <p>Title: StringDiff</p>
 * <p>Description: This class implements a string comparison algorithm that
 * allows to know the similirity sequences, etc. The main method is the diff
 * method which performs the difference algorithm and sets all the other
 * variables.</p>
 * <p>Copyright: Copyright (c) 2003</p>
 * <p>Company: Jahia Ltd</p>
 * @author Serge Huber
 * @version 1.0
 */

public class StringDiff {

    private java.util.ArrayList leftSequences;
    private java.util.ArrayList rightSequences;
    private String left;
    private String right;
    private int sequenceCount;
    private int sameCharCount;

    public StringDiff() {
    }

    /**
     * <p>Title: Sequence position</p>
     * <p>Description: This class is used to store sequence positions in a
     * string. A sequence position is compose of two coordinates, a starting
     * and an ending position for a sequence that is present in both strings.
     * These coordinates can then be used to build visual differentiators for
     * the strings, or whatever one can imagine.</p>
     * <p>Copyright: Copyright (c) 2003</p>
     * <p>Company: </p>
     * @author Serge Huber
     * @version 1.0
     */
    public class SequencePos {
        private int startPos;
        private int endPos;
        public SequencePos(int startPos, int endPos) {
            this.startPos = startPos;
            this.endPos = endPos;
        }

        public int getStartPos() {
            return this.startPos;
        }

        public int getEndPos() {
            return this.endPos;
        }
    }

    /**
     * This is the main method of this class, that actually performs the
     * matching between the two strings. The result of this operation is stored
     * in class fields such as leftSequences and rightSequences, sameCharCount,
     * sequenceCount, etc... These results stay available until the next call
     * to the diff method is done.
     * @param left the first string to compare
     * @param right the second string to compare the first one with
     */
    public void diff(String left, String right) {
        int curLeftPos = 0;
        int curRightPos = 0;
        this.sameCharCount = 0;
        this.sequenceCount = 0;

        this.leftSequences = new ArrayList();
        this.rightSequences = new ArrayList();

        this.left = left;
        this.right = right;

        // first let's handle all the special cases...

        if (left == null) {
            return;
        }
        if (right == null) {
            return;
        }

        if (left.length() < 2) {
            if (right.startsWith(left)) {
                SequencePos leftSequencePos = new SequencePos(0, left.length());
                leftSequences.add(leftSequencePos);
                rightSequences.add(leftSequencePos);
                return;
            }
        }
        if (right.length() < 2) {
            if (left.startsWith(right)) {
                SequencePos rightSequencePos = new SequencePos(0, right.length());
                leftSequences.add(rightSequencePos);
                rightSequences.add(rightSequencePos);
                return;
            }
        }

        // special cases have been handled, now to the general algorithm...

        // first we follow all the similar sequences if they are similar for
        // more than one character.
        if (
            (
            ( (curLeftPos + 1) < left.length()) &&
            ( (curRightPos + 1) < right.length()) &&
            (left.charAt(curLeftPos + 1) == (right.charAt(curRightPos + 1)))
            ) ||
            (
            ( (curLeftPos + 1) == left.length()) &&
            ( (curRightPos + 1) == right.length())
            )
            ) {

            int leftSequenceStartPos = curLeftPos;
            int rightSequenceStartPos = curRightPos;

            while ( (curLeftPos < left.length()) &&
                   (curRightPos < right.length()) &&
                   (left.charAt(curLeftPos) == right.charAt(curRightPos))) {
                System.out.print(left.charAt(curLeftPos));
                sameCharCount++;
                curLeftPos++;
                curRightPos++;
            }
            if (curLeftPos != 0) {
                sequenceCount++;
                SequencePos newLeftSequencePos = new SequencePos(
                    leftSequenceStartPos, curLeftPos);
                SequencePos newRightSequencePos = new SequencePos(
                    rightSequenceStartPos, curRightPos);
                leftSequences.add(newLeftSequencePos);
                rightSequences.add(newRightSequencePos);
            }

        }

        while (curLeftPos < left.length()) {
            // we now have differences in the two strings, lets try to find
            // another simitude join point.
            int tempRightPos = curRightPos;
            while ( (tempRightPos < right.length() &&
                     (left.charAt(curLeftPos) != right.charAt(tempRightPos)))) {
                tempRightPos++;
            }
            // if we arrived here it means we either found a new similitude or
            // we looked through the whole right string.
            if (tempRightPos == right.length()) {
                // we arrived at the end of the right string without finding
                // anything, let's move one character in the left string and
                // continue searching...
                curLeftPos++;
            } else {
                // we found similitudes again, let's eat them up only if we
                // have more than one similar character.
                if (
                    (
                    ( (curLeftPos + 1) < left.length()) &&
                    ( (tempRightPos + 1) < right.length()) &&
                    (left.charAt(curLeftPos + 1) ==
                     (right.charAt(tempRightPos + 1)))
                    ) ||
                    (
                    ( (curLeftPos + 1) == left.length()) &&
                    ( (tempRightPos + 1) == right.length())
                    )
                    ) {
                    System.out.print("?");
                    curRightPos = tempRightPos;
                    int leftSequenceStartPos = curLeftPos;
                    int rightSequenceStartPos = curRightPos;
                    while ( (curLeftPos < left.length()) &&
                           (curRightPos < right.length()) &&
                           (left.charAt(curLeftPos) == right.charAt(curRightPos))) {
                        System.out.print(left.charAt(curLeftPos));
                        sameCharCount++;
                        curLeftPos++;
                        curRightPos++;
                    }
                    SequencePos newLeftSequencePos = new SequencePos(
                        leftSequenceStartPos, curLeftPos);
                    SequencePos newRightSequencePos = new SequencePos(
                        rightSequenceStartPos, curRightPos);
                    leftSequences.add(newLeftSequencePos);
                    rightSequences.add(newRightSequencePos);
                    sequenceCount++;
                } else {
                    curLeftPos++;
                }
            }
        }

        // ok we finished looking through the left string, but the right
        // string might be longer, let's make sure we add the characters to the
        // diff list.

    }

    /**
     * Returns the array of sequence positions of similar chars for the left
     * string
     * @return an ArrayList of SequencePos objects that contain the position of
     * the sequences that were found in both strings.
     */
    public java.util.ArrayList getLeftSequences() {
        return leftSequences;
    }

    /**
     * Returns the array of sequence positions of similar chars for the right
     * string
     * @return an ArrayList of SequencePos objects that contain the position of
     * the sequences that were found in both strings.
     */
    public java.util.ArrayList getRightSequences() {
        return rightSequences;
    }

    /**
     * @return the last used left string
     */
    public String getLeft() {
        return left;
    }

    /**
     * @return the last used right string
     */
    public String getRight() {
        return right;
    }

    /**
     * @return the number of sequences that are present in both strings
     */
    public int getSequenceCount() {
        return sequenceCount;
    }

    /**
     * @return the full count of characters that are in all the sequences that
     * are the same between both strings
     */
    public int getSameCharCount() {
        return sameCharCount;
    }

    private static void displayResults(StringDiff stringDiff) {
        System.out.println("\nsameCharCount=" + stringDiff.getSameCharCount() +
                           " sequenceCount=" +
                           stringDiff.getSequenceCount());
        for (int i = 0; i < stringDiff.getLeftSequences().size(); i++) {
            SequencePos leftSequencePos = (SequencePos) stringDiff.
                getLeftSequences().get(i);
            SequencePos rightSequencePos = (SequencePos) stringDiff.
                getRightSequences().get(i);
            System.out.println("sequence " + i + " : left=[" +
                               leftSequencePos.getStartPos() + "," +
                               leftSequencePos.getEndPos() +
                               "]=[" +
                               stringDiff.getLeft().substring(leftSequencePos.
                getStartPos(),
                leftSequencePos.getEndPos()) +
                               "] right=[" + rightSequencePos.getStartPos() +
                               "," +
                               rightSequencePos.getEndPos() + "]=[" +
                               stringDiff.getRight().substring(rightSequencePos.
                getStartPos(),
                rightSequencePos.getEndPos()) +
                               "]");
        }

    }

    /**
     * Runs a few tests cases to make sure the diff algorithm is correct.
     * @param args
     */
    public static void main(String[] args) {
        StringDiff stringDiff = new StringDiff();

        // test cases.
        stringDiff.diff("select * from jahia_fields_data where iasdfd_jahia_fields_data=10 order by id_jahia_fields_datba",
                        "select * from jahia_fkjahdflields_data where id_jahiadsfa_fields_data=23 order by id_jahia_fields_data");
        displayResults(stringDiff);
        stringDiff.diff(
            "select * from jahia_fields_data where id_jahia_fields_data=10 order by",
            "select * from jahia_fields_data where id_jahia_fields_data=10 order by id_jahia_fields_data");
        displayResults(stringDiff);
        stringDiff.diff("a",
                        "a");
        displayResults(stringDiff);
        stringDiff.diff("",
                        "");
        displayResults(stringDiff);
        stringDiff.diff("",
                        "a");
        displayResults(stringDiff);
        stringDiff.diff("a",
                        "");
        displayResults(stringDiff);
        stringDiff.diff("aa",
                        "a");
        displayResults(stringDiff);
        stringDiff.diff("a",
                        "aa");
        displayResults(stringDiff);
        stringDiff.diff("aa",
                        "aa");
        displayResults(stringDiff);
        stringDiff.diff(null,
                        null);
        displayResults(stringDiff);
        stringDiff.diff("a",
                        null);
        displayResults(stringDiff);
        stringDiff.diff(null,
                        "a");
        displayResults(stringDiff);

    }

}